﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Configuration.Provider;
using System.Linq;
using System.Web.Security;

namespace ADI.Security.Membership
{
    public sealed class ADISSORoleProvider : RoleProvider
    {
        private ConnectionStringSettings connectionString;
        /*************************************************************************
        * General settings
        *************************************************************************/

        private string _applicationName;
        public override string ApplicationName
        {
            get { return _applicationName; }
            set { _applicationName = value; }
        }


        /*************************************************************************
         * Retrieval methods
         *************************************************************************/

        /// <summary>
        /// Gets all available user roles
        /// </summary>
        /// <returns>Array of all available roles</returns>
        public override string[] GetAllRoles()
        {
            string[] roles = null;
            using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
            {
                roles = (from r in adc.Role
                         where r.RegisteredApp.AppName.ToLower() == _applicationName.ToLower()
                         select r.Description).ToArray();
            }
            return roles;
        }

        /// <summary>
        /// Gets all available user roles, filtered
        /// <param name="RoleToMatch">The matching role</param>
        /// </summary>
        /// <returns>Array of all available roles</returns>
        public string[] GetAllRoles(string RoleToMatch)
        {
            string[] roles = null;
            using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
            {
                roles = (from r in adc.Role
                         where r.RegisteredApp.AppName.ToLower() == _applicationName.ToLower()
                         && r.Description.ToLower().Contains(RoleToMatch.ToLower())
                         select r.Description).ToArray();
            }
            return roles;
        }

        /// <summary>
        /// Gets the assigned roles for a particular user.
        /// </summary>
        /// <param name="username">Matching username</param>
        /// <returns>Array of assigned roles</returns>
        public override string[] GetRolesForUser(string username)
        {
            string[] roles = null;
            using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
            {
                roles = (from mg in adc.MembersInRoles
                         where mg.Role.RegisteredApp.AppName.ToLower() == _applicationName.ToLower()
                         && mg.Member.Username.ToLower() == username.ToLower()
                         select mg.Role.Description).ToArray();
            }
            return roles;
        }

        /// <summary>
        /// Gets all the users in a particular role
        /// </summary>
        public override string[] GetUsersInRole(string roleName)
        {
            string[] users = null;
            using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
            {
                users = (from m in adc.MembersInRoles
                         join r in adc.Role on m.RoleId equals r.RoleId
                         where r.RegisteredApp.AppName.ToLower() == _applicationName.ToLower()
                         && r.Description.Equals(roleName)
                         select m.Member.Username).ToArray();
            }
            return users;
        }


        /*************************************************************************
         * Create and Delete methods
         *************************************************************************/

        /// <summary>
        /// Creates a new role
        /// </summary>
        public override void CreateRole(string roleName)
        {
            // No need to add if it already exists
            if (!RoleExists(roleName))
            {
                using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
                {
                    Role g = new Role();
                    g.RoleId = Guid.NewGuid();
                    g.Description = roleName;
                    g.RegisteredAppId = adc.RegisteredApp.Where(r => r.AppName.ToLower() == _applicationName.ToLower()).Single().RegisteredAppId;
                    adc.Role.InsertOnSubmit(g);
                    adc.SubmitChanges();
                }
            }
        }

        /// <summary>
        /// Deletes a given role
        /// </summary>
        /// <param name="roleName">Role name to delete</param>
        /// <param name="throwOnPopulatedRole">Specifies whether the function should throw
        /// if there are assigned users to this role</param>
        /// <returns>True if successful. Defaults to false</returns>
        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            // Return status. Defaults to false.
            bool ret = false;

            // You can only delete an existing role
            if (RoleExists(roleName))
            {
                try
                {
                    using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
                    {
                        if (throwOnPopulatedRole)
                        {
                            Guid[] users = (from mg in adc.MembersInRoles
                                            where mg.Role.RegisteredApp.AppName.ToLower() == _applicationName.ToLower()
                                            && mg.Role.Description.ToLower() == roleName.ToLower()
                                            select mg.Member.UserId).ToArray();

                            if (users.Count() > 0)
                                throw new ProviderException("Non puoi eliminare un ruolo con utenti inseriti");
                        }

                        Role g = (from roles in adc.Role
                                  where roles.RegisteredApp.AppName.ToLower() == _applicationName.ToLower()
                                  && roles.Description == roleName
                                  select roles).FirstOrDefault();

                        adc.Role.DeleteOnSubmit(g);
                        adc.SubmitChanges();

                        ret = true;
                    }
                }
                catch (Exception ex) { throw ex; }
            }

            return ret;
        }


        /*************************************************************************
         * Assign/Remove methods
         *************************************************************************/

        /// <summary>
        /// Adds a collection of users to a collection of corresponding roles
        /// </summary>
        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            // Get the actual available roles
            string[] allRoles = GetAllRoles();

            // See if any of the given roles match the available roles
            IEnumerable<string> roles = allRoles.Intersect(roleNames);

            // There were some roles left after removing non-existent ones
            if (roles.Count() > 0)
            {
                // Cleanup duplicates first
                RemoveUsersFromRoles(usernames, roleNames);

                using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
                {
                    // Get the user IDs
                    List<Guid> mlist = (from members in adc.Member
                                        where usernames.Contains(members.Username)
                                        select members.UserId).ToList();

                    // Get the role IDs
                    List<Guid> glist = (from r in adc.Role
                                        where r.RegisteredApp.AppName.ToLower() == _applicationName.ToLower()
                                        && roleNames.Contains(r.Description)
                                        select r.RoleId).ToList();

                    // Fresh list of user-role assignments
                    List<MembersInRoles> mglist = new List<MembersInRoles>();
                    foreach (var m in mlist)
                    {
                        foreach (var g in glist)
                        {
                            MembersInRoles mg = new MembersInRoles();
                            mg.MembersInRolesId = Guid.NewGuid();
                            mg.UserId = m;
                            mg.RoleId = g;
                            mg.Manager = false;
                            mglist.Add(mg);
                        }
                    }

                    adc.MembersInRoles.InsertAllOnSubmit(mglist);
                    adc.SubmitChanges();
                }
            }
        }

        /// <summary>
        /// Remove a collection of users from a collection of corresponding roles
        /// </summary>
        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            // Get the actual available roles
            string[] allRoles = GetAllRoles();

            // See if any of the given roles match the available roles
            IEnumerable<string> roles = allRoles.Intersect(roleNames);

            // There were some roles left after removing non-existent ones
            if (roles.Count() > 0)
            {
                using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
                {
                    List<MembersInRoles> mg = (from members in adc.MembersInRoles
                                               where members.Role.RegisteredApp.AppName.ToLower() == _applicationName.ToLower()
                                               && usernames.Contains(members.Member.Username) &&
                                               roleNames.Contains(members.Role.Description)
                                               select members).ToList();

                    adc.MembersInRoles.DeleteAllOnSubmit(mg);
                    adc.SubmitChanges();
                }
            }
        }

        /// <summary>
        /// Make the given username a manager or not
        /// </summary>
        /// <param name="username">user username</param>
        /// <param name="roleName">which role</param>
        /// <param name="manager">is mananager or not</param>
        public void MakeUserManagerForRole(string username, string roleName, bool manager)
        {
            using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
            {
                MembersInRoles mr = adc.MembersInRoles.Where(m => m.Role.RegisteredApp.AppName.ToLower() == _applicationName.ToLower() && m.Member.Username.Equals(username) && m.Role.Description.Equals(roleName)).SingleOrDefault();
                if (mr == null) throw new Exception("Member not found");

                mr.Manager = manager;
                adc.SubmitChanges();
            }
        }


        /*************************************************************************
         * Searching methods
         *************************************************************************/

        /// <summary>
        /// Checks if a given username is in a particular role
        /// </summary>
        public override bool IsUserInRole(string username, string roleName)
        {
            // Return status defaults to false
            bool ret = false;

            if (RoleExists(roleName))
            {
                using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
                {
                    int c = (from m in adc.MembersInRoles
                             where m.Role.RegisteredApp.AppName.ToLower() == _applicationName.ToLower()
                             && m.Member.Username == username &&
                             m.Role.Description.ToLower() == roleName.ToLower()
                             select m).Count();

                    if (c > 0)
                        ret = true;
                }
            }

            return ret;
        }

        /// <summary>
        /// Finds a set of users in a given role
        /// </summary>
        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            string[] users = null;
            using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
            {
                users = (from m in adc.MembersInRoles
                         join r in adc.Role on m.RoleId equals r.RoleId
                         where r.RegisteredApp.AppName.ToLower() == _applicationName.ToLower()
                         && r.Description.Equals(roleName)
                         && m.Member.Username.Contains(usernameToMatch)
                         select m.Member.Username).ToArray();
            }
            return users;
        }

        /// <summary>
        /// Check if a user is a manager
        /// </summary>
        /// <param name="username">user to check</param>
        /// <param name="roleName">which role</param>
        /// <returns>if user is manager for given role</returns>
        public bool IsUserManager(string username, string roleName)
        {
            bool isManager = false;
            using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
            {
                var manager = adc.MembersInRoles.Where(m => m.Role.RegisteredApp.AppName.ToLower() == _applicationName.ToLower()
                                                            && m.Member.Username.ToLower() == username.ToLower()
                                                            && m.Role.Description.ToLower() == roleName.ToLower()).SingleOrDefault();
                if (manager == null) return false;

                isManager = manager.Manager;
            }
            return isManager;
        }

        /// <summary>
        /// Checks if a given role already exists in the database
        /// </summary>
        /// <param name="roleName">Role name to search</param>
        /// <returns>True if the role exists. Defaults to false.</returns>
        public override bool RoleExists(string roleName)
        {
            return GetAllRoles().Contains(roleName);
        }

        /*************************************************************************
         * Initialization
         *************************************************************************/

        /// <summary>
        /// Initialize the RoleProvider
        /// </summary>
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            if (name == null || name.Length == 0)
                name = "ADISSORoleProvider";

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "ADISSO Role Provider");
            }

            if (String.IsNullOrEmpty(config["applicationName"]))
                throw new ArgumentNullException("applicationName");

            _applicationName = config["applicationName"];

            if (String.IsNullOrEmpty(config["connectionStringName"]))
                throw new ArgumentNullException("connectionStringName");

            connectionString = ConfigurationManager.ConnectionStrings[config["connectionStringName"]];


            using (ADISSODataContext adc = new ADISSODataContext(connectionString.ConnectionString))
            {
                var appNameExist = adc.RegisteredApp.Where(r => r.AppName.ToLower() == config["applicationName"].ToLower()).SingleOrDefault();
                if (appNameExist == null)
                    throw new Exception(string.Format("L'applicazione {0} non esiste nel database", config["applicationName"]));
            }

            // Initialize base class
            base.Initialize(name, config);
        }
    }
}